package com.zhisheng.orders.service.impl;

import cn.hutool.core.lang.Snowflake;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.GoodsDetail;
import com.alipay.api.internal.util.AlipaySignature;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhisheng.dto.order.OrderDto;
import com.zhisheng.exception.MyBusinessException;
import com.zhisheng.orderItem.entity.OrderItems;
import com.zhisheng.orders.dto.AliPayDto;
import com.zhisheng.orders.entity.Orders;
import com.zhisheng.orders.feign.ProductsFeign;
import com.zhisheng.orders.mapper.OrdersMapper;
import com.zhisheng.orders.service.OrderItemsService;
import com.zhisheng.orders.service.OrdersService;
import com.zhisheng.orders.utils.MyAliPayUtils;
import com.zhisheng.result.MyResponseResult;
import com.zhisheng.users.entity.Users;
import com.zhisheng.utils.MyThreadLocal;
import io.seata.tm.api.GlobalTransaction;
import io.seata.tm.api.GlobalTransactionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * (Orders)表服务实现类
 *
 * @author 全哥
 * @since 2025-02-24 10:35:47
 */
@Service("ordersService")
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements OrdersService {


    // 注入一个 雪花算法的对象
    @Autowired
    private Snowflake snowflake ;

    @Autowired
    private StringRedisTemplate stringRedisTemplate ;

    @Autowired
    private OrderItemsService orderItemsService ;

    @Autowired
    private ProductsFeign productsFeign;

    @Autowired
    private MyAliPayUtils myAliPayUtils;


    @Override
    public Long createOrderId() {
        /*
            1: 先按照我们自己的规则(利用hutool工具提供的雪花算法)生成一个订单id
            2: 将订单id保存到redis中,以id为key,以"ok"为value
            3: 将订单id返回给前端
         */
        //1: 先按照我们自己的规则(利用hutool工具提供的雪花算法)生成一个订单id
        Long id = snowflake.nextId();

        // 3: 将订单id返回给前端
        return id;
    }

    @Override
    //@GlobalTransactional
    public String createOrderAndPay(OrderDto orderDto) throws Exception {
        /*
            1: 根据订单id,从redis中获取订单id,如果获取不到,则说明订单已经支付过了,直接返回
            2: 根据订单id,从redis中获取订单id,如果获取到,则说明订单是第一次提交,从redis中删除这个订单号
            3: 从ThreadLocal中获取用户id
            4: 创建订单,并保存到数据库中(包含收货人相关信息)
            5: 将map集合中的数据,传递给订单项服务,完成订单项的添加
            6: 将map集合中的数据传递给 商品微服务,进行库存扣减(使用seata控制整体流程)
            7: 将订单信息按第三方支付平台的要求,组织参数,完成支付宝支付
         */
        // 1: 根据订单id,从redis中获取订单id,如果获取不到,则说明订单已经支付过了,直接返回
        // 2: 根据订单id,从redis中获取订单id,如果获取到,则说明订单是第一次提交,从redis中删除这个订单号
        // 获取或创建一个分布式事务管理对象
        GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
        tx.begin();
        MyResponseResult<String> result = getMyResponseResult(orderDto);
        if(result.getCode() == 500){
            // 说明扣减库存失败了,则抛出异常即可;
            // 手动回滚
            tx.rollback();
            throw new MyBusinessException("在订单服务中收到了扣减库存失败的结果,具体的信息是:"+result.getMsg());
        }
        // 手动提交
        tx.commit();
        // TODO 7: 将订单信息按第三方支付平台的要求,组织参数,完成支付宝支付
//        int i = 1/0;

        String res = toAliPay(orderDto.getOrderId());
        return res;
    }

    @Override
    public String toAliPay(Long oid) throws AlipayApiException {
        /*
            1: 根据订单id,从数据库中获取订单对象(订单关联的订单项,订单项关联的商品对象)
            2: 将订单信息按第三方支付平台的要求,组织参数(AliPayDto)
            3: 调用支付宝专用工具,完成支付
            4: 将工具返回的字符串(html表单)通过html的形式响应给浏览器;
         */
        //1: 根据订单id,从数据库中获取订单对象(订单关联的订单项,订单项关联的商品对象)
        Orders orders = getById(oid);
        if(!"0".equals(orders.getStatus()) ){
            throw new MyBusinessException("该订单已经支付过了,请勿重复支付");
        }
        // 1.1: 根据订单id,查询订单项集合
        List<OrderItems> orderItems =orderItemsService.selectOrderItemsByOrderId(oid);
        Double sum = 0.0;
        for (OrderItems orderItem : orderItems) {
            sum+=orderItem.getUnitPrice();
        }
        List<GoodsDetail> list = orderItems.stream().map(orderItem -> {
            GoodsDetail goodsDetail = new GoodsDetail();
            goodsDetail.setGoodsId(orderItem.getProductId().toString());
            goodsDetail.setGoodsName(orderItem.getProduct().getName());
            goodsDetail.setQuantity(Long.valueOf(orderItem.getQuantity()));
            goodsDetail.setPrice(orderItem.getUnitPrice().toString());
            goodsDetail.setShowUrl("https://tse3-mm.cn.bing.net/th/id/OIP-C.5AmeaglwRvHzzdbFVQcJ-QHaNK?rs=1&pid=ImgDetMain");
            return goodsDetail;
        }).toList();
        //2: 将订单信息按第三方支付平台的要求,组织参数(AliPayDto)
        LocalDateTime localDateTime = LocalDateTime.now().plusYears(1);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String format = localDateTime.format(formatter);
        AliPayDto aliPayDto = new AliPayDto(orders.getId().toString(), sum.toString(), orders.getName()+"-"+orders.getPhone(), list, format);
        // 3: 调用支付宝专用工具,完成支付
        String pay = myAliPayUtils.toAliPay(aliPayDto);
        //4: 将工具返回的字符串(html表单)通过html的形式响应给浏览器;
        return pay;

    }

    @Override
    public String callback(Map<String, String> paramsMap) throws AlipayApiException {
        /*
            支付宝支付之后,会回调这个方法
            map中存放的都是支付宝给我们的信息
            我们需要对这些信息的合法性进行校验(验签)
            如果验签成功,则修改订单的状态为 已支付!
            如果验签失败,则返回一个错误信息给支付宝
         */
        boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap, myAliPayUtils.getAlipayPublicKey(), "UTF-8", "RSA2"); //调用SDK验证签名
        if(signVerified){
            // 成功
            //则修改订单的状态为 已支付!
            LambdaUpdateWrapper<Orders> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(Orders::getStatus, "1");
            updateWrapper.eq(Orders::getId, paramsMap.get("out_trade_no"));
            update(updateWrapper);
            return paramsMap.get("paramsMap")+":订单状态已更新为:已支付!";
        }
        return paramsMap.get("paramsMap")+" 号订单验签失败!";
    }

    // 这个方法是专门负责执行扣减库存的逻辑并带了一个本地事务
    @Transactional
    public MyResponseResult<String> getMyResponseResult(OrderDto orderDto) {
        checkOrderId(orderDto.getOrderId());
        //3: 从ThreadLocal中获取用户id
        //4: 创建订单,并保存到数据库中(包含收货人相关信息)
        Users users = MyThreadLocal.get();
        Orders orders = new Orders(orderDto.getOrderId(),users.getId(),"0",new Date(),0, orderDto.getAddress(), orderDto.getName(), orderDto.getPhone());
        save(orders);
        //5: 将map集合中的数据,传递给订单项服务,完成订单项的添加
        orderItemsService.mySaveOrderItem(orderDto.getGoods(),orders.getId());
        // 6: 将map集合中的数据传递给 商品微服务,进行库存扣减(使用seata控制整体流程)
        Map<Long, Integer> goods = orderDto.getGoods();
        HashMap<Integer, Integer> hashMap = new HashMap<>();
        for (Long key : goods.keySet()) {
            hashMap.put(key.intValue(),goods.get(key));
        }
        // 由于我们自己编写了一个全局统一异常处理,因此当我们进行远程调用的时候,即使扣减库存的过程中发生了业务异常,也会被全局统一异常处理捕获到,因此在这里我们无法直接获取到哪个原始的异常信息!!!只能获取到的是统一异常处理后的结果 ;(MyResponseResult.fail(.....))
        MyResponseResult<String> result = productsFeign.kouJian(hashMap);
        return result;
    }

    // 这个方法,专门负责id的校验,如果检测成功,则从redis中删除id,如果检测失败,则抛出异常
    private void checkOrderId(Long orderId) {
        //String s = stringRedisTemplate.opsForValue().getAndDelete("orderId:" + orderId);
        // 同一个订单号只有第一次提交的时候,才能存入redis,后续的重复的提交将无法存进去!!!
        Boolean b = stringRedisTemplate.opsForValue().setIfAbsent("orderId:" + orderId, "ok", 5, TimeUnit.MINUTES);
        if(!b){
            throw new MyBusinessException("订单已经提交过了!请勿重复提交!");
        }
    }
}

